package com.zyy.seckill.controller;

import com.wf.captcha.ArithmeticCaptcha;
import com.zyy.seckill.config.AccessLimit;
import com.zyy.seckill.entity.SeckillOrder;
import com.zyy.seckill.entity.User;
import com.zyy.seckill.exception.GlobalException;
import com.zyy.seckill.rabbitmq.MQSender;
import com.zyy.seckill.service.IGoodsService;
import com.zyy.seckill.service.IOrderService;
import com.zyy.seckill.service.ISeckillOrderService;
import com.zyy.seckill.utils.JsonUtil;
import com.zyy.seckill.vo.GoodsVo;
import com.zyy.seckill.vo.RespBean;
import com.zyy.seckill.vo.RespEnum;
import com.zyy.seckill.vo.SeckillMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 安全性问题的解决 :
 * 1.接口地址的隐藏
 * 2.验证码隔离脚本
 * 3.接口防刷(同一个用户一时间不可以重复点击)  :规则大致在qps的 70% ~ 80%
 *
 * @Author: zyy
 * @DateTime: 2021/10/4 15:56
 */
@Controller
@RequestMapping("/seckill")
@Slf4j
public class SecKillController implements InitializingBean {  // 实现在系统初始化之前进行操作的接口
    @Autowired
    IGoodsService goodsService;
    @Autowired
    ISeckillOrderService seckillOrderService;
    @Autowired
    private IOrderService orderService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private MQSender mqSender;
    @Autowired
    private RedisScript<Long> redisScript;

    // 声明处理redis库存的内存标记的map
    private Map<Long, Boolean> EmptyStockMap = new HashMap<>();


/**
 * 秒杀功能实现 v1.0
 * <p>
 * 优化前: qps :400
 * <p>
 * 缓存后qps: 905
 *
 * 优化qps: 1800
 *
 *
 * @return
 */
//    @RequestMapping("/doSeckill")
//    public String doSeckill(Model model,
//                            User user,
//                            Long goodsId) {
//        if (user == null) {
//            return "login";
//        }
//        model.addAttribute("user", user);
//        GoodsVo goodsVo = goodsService.findGoodsVoById(goodsId);
//
//        // 判断是否重复抢购
//        QueryWrapper<SeckillOrder> queryWrapper
//                = new QueryWrapper<>();
//        queryWrapper.eq("user_id", user.getId());
//        queryWrapper.eq("goods_id", goodsId);
//
//        // 判断库存
//        if (goodsVo.getStockCount() < 1) {
//            model.addAttribute("errmsg", RespEnum.EMPTY_STOCK.getMessage());
//            return "seckillFail";
//        }
//
//        SeckillOrder seckillOrder =
//                seckillOrderService.getOne(queryWrapper);
//        if (seckillOrder != null) {
//            model.addAttribute("errmsg", RespEnum.REPEAT_STOCK.getMessage());
//            return "seckillFail";
//        }
//        // 开始秒杀
//        Order order = orderService.seckill(user, goodsVo);
//        model.addAttribute("order", order);
//        model.addAttribute("goods", goodsVo);
//
//        return "orderDetail";
//    }

    /**
     * 秒杀功能实现 v2.0 页面静态化
     * v3.0 接口优化
     *
     * @param path    : 获取当前用户的接口访问地址
     * @param user
     * @param goodsId
     * @return
     */
    @PostMapping("/{path}/doSeckill")
    @ResponseBody
    public RespBean doSeckill2(@PathVariable String path, User user, Long goodsId) {

        if (user == null) {
            return RespBean.error(RespEnum.SESSION_ERROR);
        }

        ValueOperations valueOperations = redisTemplate.opsForValue();
        // 检查当前用户接口路径是否正确
        boolean check = orderService.checkPath(user, goodsId, path);
        if (!check) {
            return RespBean.error(RespEnum.REQUEST_NOT_EXIST);
        }

        //判断是否重复抢购(redis实现)
        SeckillOrder seckillOrder = (SeckillOrder) valueOperations.
                get("order:" + user.getId() + ":" + goodsId);

        if (seckillOrder != null) {
            return RespBean.error(RespEnum.REPEAT_STOCK);
        }

        // 通过内存标记减少redis访问的判断
        if (EmptyStockMap.get(goodsId)) {
            return RespBean.error(RespEnum.EMPTY_STOCK);
        }

        // 预减库存的操作  -->使用 lua脚本再次进行优化
//        Long decrement = valueOperations.decrement("seckillGoods:" + goodsId);
        Long decrement = (Long) redisTemplate
                .execute(redisScript, Collections.singletonList("seckillGoods:" + goodsId), Collections.EMPTY_LIST);

        if (decrement < 0) { // 判断预减库存是否为0
            EmptyStockMap.put(goodsId, true);// 修改当前商品库存判断的map的value值
            valueOperations.increment("seckillGoods:" + goodsId);
            return RespBean.error(RespEnum.EMPTY_STOCK);
        }

        // 创建封装的消息对象  -->做秒杀需要用户信息和商品(可以通过商品进行获取)
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);

        // 通过rarbbitmq进行消费
        mqSender.sendScekillMessage(JsonUtil.object2JsonStr(seckillMessage));

        return RespBean.success(0);

        /**判断库存 是否>0
         GoodsVo goodsVo = goodsService.findGoodsVoById(goodsId);

         if (goodsVo.getStockCount() < 1) {
         //            model.addAttribute("errmsg", RespEnum.EMPTY_STOCK.getMessage());
         return RespBean.error(RespEnum.EMPTY_STOCK);
         }
         */

        /**判断是否重复抢购 (查询数据库)
         QueryWrapper<SeckillOrder> queryWrapper
         = new QueryWrapper<>();
         queryWrapper.eq("user_id", user.getId());
         queryWrapper.eq("goods_id", goodsId);

         SeckillOrder seckillOrder =
         seckillOrderService.getOne(queryWrapper);
         if (seckillOrder != null) {
         model.addAttribute("errmsg", RespEnum.REPEAT_STOCK.getMessage());
         return RespBean.error(RespEnum.REPEAT_STOCK);
         }
         */
        /** 开始秒杀
         * Order order = orderService.seckill(user,goodsVo);
         */
    }

    /**
     * 获取秒杀结果
     *
     * @param user
     * @param goodsId
     * @return orderID:成功 -1秒杀失败 0 排队中
     */
    @RequestMapping(value = "/result")
    @ResponseBody
    public RespBean getResult(User user, Long goodsId) {
        if (user == null) {
            return RespBean.error(RespEnum.SESSION_ERROR);
        }
        Long orderId = seckillOrderService.getResult(user, goodsId);
        return RespBean.success(orderId);
    }

    /**
     * 获取 秒杀地址
     *
     * @param user
     * @param goodsId
     * @return
     */
    @RequestMapping("/path")
    @ResponseBody
    @AccessLimit(seconds=5,maxCount=5,needLogin =true)
    public RespBean getPath(User user, String goodsId,String captcha,
                            HttpServletRequest request) {
        if (user == null) {
            return RespBean.error(RespEnum.SESSION_ERROR);
        }

/** 处理接口流量 设置 5s访问 5次 ;使用计数器算法 通过redis实现 根据用户Id和访问地址的唯一标志 并且设置过期时间5s 和value为0,每次访问都increment(),使用自定义注解代替
        ValueOperations valueOperations = redisTemplate.opsForValue();

        String uri = request.getRequestURI();
        captcha="0";
        Integer count = (Integer)valueOperations.get(uri + ":" + user.getId());
        if (count ==null){
            valueOperations.set(uri + ":" + user.getId(),1,5,TimeUnit.SECONDS);
        }else if (count<=5){
            valueOperations.increment(uri + ":" + user.getId());
        }else {
            return RespBean.error(RespEnum.ACCESS_LIMIT);
        }
*/
        boolean check =orderService.checkCaptcha(user,goodsId,captcha);
        if (!check){
            return RespBean.error(RespEnum.CAPTCHA_ERROR);
        }
        String str = orderService.createPath(user, goodsId);
        return RespBean.success(str);
    }

    /**
     * 验证码验证
     *
     * @param user
     * @param goodsId
     * @param response
     */
    @RequestMapping(value = "/captcha", method = RequestMethod.GET)
    public void verifyCode(User user, Long goodsId,
                           HttpServletResponse response,
                           HttpServletRequest request) {
        if (user == null || goodsId < 0) {
            throw new GlobalException(RespEnum.REQUEST_NOT_EXIST);
        }
        // 设置请求头为图片输出类型
        response.setContentType("image/jpg");
        response.setHeader("Pargam","No-cache");
        response.setHeader("Cache-Control","No-cache");
        response.setDateHeader("Expires",0);//设置失效时间

        // 生成验证码并且存入redis中
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 32, 3);
        redisTemplate.opsForValue().set("capche:"+user.getId()+":"+goodsId,captcha.text(),300, TimeUnit.SECONDS);

        try {
            captcha.out(response.getOutputStream());
        } catch (IOException e) {
            log.error("验证码生成失败",e.getMessage());
        }
    }


    /**
     * 系统初始化的操作  -->把秒杀库存和商品加载到redis中,redis中进行预键值的操作
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVo> list = goodsService.findGoodsVo();
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        list.forEach((goodsVo) -> {
            redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());
            EmptyStockMap.put(goodsVo.getId(), false); // 设定内存标记-->每件秒杀商品的id作为key 初始值都是 false代表库存不为空
        });
    }
}
